package com.xqboss.apps.service.consumer;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.xqboss.apps.constant.Config;
import com.xqboss.apps.constant.LockKey;
import com.xqboss.apps.domain.consumer.ConsumerTeam;
import com.xqboss.apps.domain.consumer.TeamPunchStat;
import com.xqboss.apps.domain.punch.UserTeamSettleRecord;
import com.xqboss.apps.enums.consumer.ConsumerTeamMemberTypeEnum;
import com.xqboss.apps.enums.consumer.ConsumerTeamRoleEnum;
import com.xqboss.apps.enums.user.UserWalletEnum;
import com.xqboss.apps.enums.user.WalletChangeTypeEnum;
import com.xqboss.apps.mapper.consumer.TeamPunchStatMapper;
import com.xqboss.apps.service.punch.PunchUserRecordService;
import com.xqboss.apps.service.user.SysUserWalletService;
import com.xqboss.apps.service.user.UserTeamSettleRecordService;
import com.xqboss.apps.vo.consumer.ConsumerTeamMemberFamilyVo;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.utils.StringUtils;
import com.xqboss.system.service.ISysConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Trent
 * @date 2023/9/8
 */
@Service
@Slf4j
public class TeamPunchStatService extends ServiceImpl<TeamPunchStatMapper, TeamPunchStat> {

    @Autowired
    private SysUserWalletService userWalletService;

    @Autowired
    private PunchUserRecordService punchUserRecordService;

    @Autowired
    private UserTeamSettleRecordService userTeamSettleRecordService;

    @Autowired
    private ConsumerTeamService teamService;

    @Autowired
    private ISysConfigService sysConfigService;

    @Autowired
    private ConsumerTeamMemberService consumerTeamMemberService;

    private static final BigDecimal ONE_INCOME = BigDecimal.ONE;
    private static final BigDecimal ZERO_INCOME = BigDecimal.ZERO;

    /**
     * 团队今日是否已经统计
     * @param teamId
     * @return
     */
    public boolean isTodayStat(Integer teamId, LocalDate date){
        int count = (int) count(new LambdaQueryWrapper<TeamPunchStat>()
                .eq(TeamPunchStat::getTeamId, teamId)
                .eq(TeamPunchStat::getStatDate, date));
        return count > 0;
    }

    /**
     * 分发用户队伍打卡收益
     *
     * 主成员抽成 ＝ 家庭成员额外空投 × 抽成比例
     *
     * 主成员额外空投 ＝ 额外空投 ＋ 主成员总抽成
     *
     * 队长抽成 ＝ 主成员额外空投 × 抽成比例
     *
     * 家庭成员最终额外空投 ＝ 额外空投 － 主成员抽成
     *
     * 主成员最终额外空投 ＝ 额外空投 ＋ 主成员总抽成 － 队长抽成
     *
     * 队长最终额外空投 ＝ 额外空投 ＋ 队长总抽成
     *
     * A为主成员（打卡热豆100），B（打卡热豆50）、C为家庭成员（打卡热豆80）、D为队长（打卡热豆60），
     * 队伍热豆增幅为10%，主成员抽成5%，队长抽成2%，
     * 获得热豆空投分别为：A（100 × 10% ＝ 10）、B（50 × 10% ＝5）、C（80 × 10% ＝ 8），D（60 × 10% ＝ 6），
     * 最终获得额外热豆空投为：A {（10 ＋ 5 × 5% ＋ 8 × 5% ）× （100% - 2%）}、B { 5 × （100% － 5%）}、C  { 8 ×（100% － 5%）}，D { 6 ＋（10 ＋ 5 × 5% ＋ 8 × 5% ）× 2% } 四舍五入
     *
     *
     * @param date 分发的日期
     */
    @RedisLock(lockKey = LockKey.LOCK_TEAM_INCOME_DISPATCH)
    public void dispatchUserTeamIncome(@LockValue LocalDate date) {
        BigDecimal main = sysConfigService.getValue(Config.Sys.Team.家庭主成员奖励抽成);
        BigDecimal captain = sysConfigService.getValue(Config.Sys.Team.队长奖励抽成);
        int adminRole = ConsumerTeamRoleEnum.ADMINISTRATOR.getValue();//管理员角色标识
        ConsumerTeamMemberTypeEnum mainType = ConsumerTeamMemberTypeEnum.TYPE_MAIN;//主成员枚举值
        int page = 1, size = 100;
        while(true){
            Page<Object> pageHelper = PageHelper.startPage(page, size);
            // 获取指定日期还没有结算的列表
            List<TeamPunchStat> statList = list(new LambdaQueryWrapper<TeamPunchStat>()
                    .eq(TeamPunchStat::getStatDate, date)
                    .eq(TeamPunchStat::getIsSettle, false));
            pageHelper.close();
            for (TeamPunchStat stat : statList) {
                ConsumerTeam team = teamService.getById(stat.getTeamId());
                // 开始结算统计
                if(stat.getMemberGain().compareTo(BigDecimal.ZERO) > 0){
                    // 有增益才结算
                    String teamName = team.getName();//队伍名称
                    // 获取打卡用户列表
                    List<Long> userList = punchUserRecordService.getTeamPunchUserList(stat.getTeamId(), date);
                    // 获取队伍所有成员基本信息,包括当日被解除家庭关系的成员（上面一步【获取打卡用户列表】只考虑到队伍中打了卡的用户，若 队长【管理员】或组长【主成员】当日没有打卡也要有收益）
                    List<ConsumerTeamMemberFamilyVo> teamMembers = consumerTeamMemberService.selectTeamMembers(stat.getTeamId(), date);
                    // 按成员用户id转为map
                    Map<Long, ConsumerTeamMemberFamilyVo> teamMembersMap = teamMembers.stream().collect(Collectors.toMap(ConsumerTeamMemberFamilyVo::getUserId, Function.identity()));
                    // 按主成员用户id转为map，先封装每个主成员的家庭成员用户id
                    Map<Long, List<Long>> mainMemberMap = groupByMainMember(teamMembers);
                    // 针对队长（role：1_队长）、主成员（type：1_主成员）、非主成员（type：2_爸爸 3_妈妈 4_配偶）采用不同结算方式
                    for (Map.Entry<Long, ConsumerTeamMemberFamilyVo> entry : teamMembersMap.entrySet()) {
                        Long userId = entry.getKey();
                        ConsumerTeamMemberFamilyVo info = entry.getValue();
                        boolean isPunch = userList.contains(userId);//判断当日此用户是否打卡
                        Integer role = info.getRole();//用户角色（1_管理员（队长）；2_普通成员）
                        ConsumerTeamMemberTypeEnum type = info.getType();//用户类型（1_主成员 2_爸爸 3_妈妈 4_配偶）
                        
                        if(isPunch && type != mainType && role != adminRole){
                            //如果用户今日打卡了，并且他不是 组长【主成员】、不是队长，那么进行家庭成员（2_爸爸 3_妈妈 4_配偶）打卡的收益结算
                            punchAndUnMainMemberIncomeSettlement(date, stat, teamName, userId,main);
                        }else if(isPunch && type == mainType && role != adminRole){
                            //如果用户今日打卡了，并且他是 组长【主成员】、不是队长，那么进行组长打卡的收益结算
                            punchAndMainMemberIncomeSettlement(date, stat, teamName, userId, main, captain, mainMemberMap.get(userId),userList);
                        }else if(!isPunch && type == mainType && role != adminRole){
                            //如果用户今日没打卡，并且他是 组长【主成员】，那么进行组长没打卡的收益结算
                            unPunchAndMainMemberIncomeSettlement(date, stat, teamName, userId, main, captain, mainMemberMap.get(userId),userList);
                        }else if(isPunch){
                            //如果用户今日打卡了，并且他是 队长【管理员】，那么进行队长打卡的收益结算
                            punchAndAdminMemberIncomeSettlement(date, stat, teamName, userId, main, captain, mainMemberMap,userList);
                        }else if(role == adminRole){
                            //如果用户今日没打卡，但是他是 队长【管理员】，那么进行队长没打卡的收益结算
                            unPunchAndAdminMemberIncomeSettlement(date, stat, teamName, userId, main, captain, mainMemberMap,userList);
                        }
                    }
                }
                update(new LambdaUpdateWrapper<TeamPunchStat>()
                        .set(TeamPunchStat::getIsSettle, true)
                        .set(TeamPunchStat::getSettleTime, LocalDateTime.now())
                        .eq(TeamPunchStat::getId, stat.getId()));
            }
            if(statList.size() < size){
                return;
            }
        }
    }

    /**
     * 把 队伍中的所有成员 按主成员 进行分组，转为 <主成员用户id, 家庭成员用户id集合>
     *
     * @param teamMembers 队伍中的所有成员
     */
    private Map<Long, List<Long>> groupByMainMember(List<ConsumerTeamMemberFamilyVo> teamMembers) {
        // 1.过滤出主成员
        List<ConsumerTeamMemberFamilyVo> mainMembers = teamMembers.stream().filter(t -> t.getType() == ConsumerTeamMemberTypeEnum.TYPE_MAIN).collect(Collectors.toList());
        // 2.过滤出每个主成员的家庭成员
        List<Long> familyMembersUserIdList;
        Map<Long, List<Long>> familyMembersUserIdMap = new HashMap<>(mainMembers.size());
        for (ConsumerTeamMemberFamilyVo mainMember : mainMembers) {
            familyMembersUserIdList = teamMembers.stream().filter(t -> Objects.equals(mainMember.getUserId(), t.getParentId())).map(ConsumerTeamMemberFamilyVo::getUserId).collect(Collectors.toList());
            familyMembersUserIdMap.put(mainMember.getUserId(),familyMembersUserIdList);
        }
         return  familyMembersUserIdMap;
    }


    /**
     * 非主成员打卡当日收益结算
     *
     * 家庭成员最终额外空投 ＝ 额外空投 － 主成员抽成
     *
     * 非主成员——家庭成员（2_爸爸 3_妈妈 4_配偶）
     *
     * @param date 当日
     * @param stat 队伍打卡统计
     * @param teamName 队伍名称
     * @param userId 非主成员用户id
     * @param main 家庭主成员奖励抽成
     */
    private void punchAndUnMainMemberIncomeSettlement(LocalDate date, TeamPunchStat stat, String teamName, Long userId, BigDecimal main) {
        try{
            // 计算用户队伍内打卡总收益
            BigDecimal totalIncome = punchUserRecordService.getUserTotalIncome(userId, Long.valueOf(stat.getTeamId()), date);
            // 计算发放积分（额外空投 － 主成员抽成）
            BigDecimal income = stat.getMemberGain().multiply(totalIncome).multiply(ONE_INCOME.subtract(main)).setScale(2, RoundingMode.HALF_UP);
            // 记录收益结算和发放积分
            grantIncome(stat, teamName, userId, totalIncome, income,ZERO_INCOME);
        }catch (Exception e){
            log.error("[非主成员打卡]用户{}收益结算失败！", userId, e);
        }
    }

    /**
     * 主成员打卡当日收益结算
     *
     * 主成员抽成 ＝ 家庭成员额外空投 × 抽成比例
     *
     * 主成员额外空投 ＝ 额外空投 ＋ 主成员总抽成
     *
     * 主成员最终额外空投 ＝ 额外空投 ＋ 主成员总抽成 － 队长抽成
     *
     * 主成员——组长
     *
     * @param date 当日
     * @param stat 队伍打卡统计
     * @param teamName 队伍名称
     * @param userId 用户id
     * @param main 家庭主成员奖励抽成
     * @param captain 队长奖励抽成
     * @param familyMemberUserIds 家庭成员（2_爸爸 3_妈妈 4_配偶）用户id集合
     * @param userList 队伍中打了卡的用户id集
     */
    private void punchAndMainMemberIncomeSettlement(LocalDate date, TeamPunchStat stat, String teamName, Long userId, BigDecimal main, BigDecimal captain, List<Long> familyMemberUserIds,List<Long> userList) {
        try{
            // 计算用户队伍内打卡总收益
            BigDecimal totalIncome = punchUserRecordService.getUserTotalIncome(userId, Long.valueOf(stat.getTeamId()), date);
            // 计算发放积分
            // 1.额外空投
            BigDecimal income = stat.getMemberGain().multiply(totalIncome);
            // 2.主成员总抽成
            BigDecimal mainIncome = getMainIncome(date,stat.getMemberGain(),main,familyMemberUserIds,stat.getTeamId(),userList);
            // 3.队长抽成
            BigDecimal finalIncome = income.add(mainIncome).multiply(ONE_INCOME.subtract(captain)).setScale(2, RoundingMode.HALF_UP);
            // 记录收益结算和发放积分
            grantIncome(stat, teamName, userId, totalIncome, finalIncome, mainIncome.setScale(2, RoundingMode.HALF_UP));
        }catch (Exception e){
            log.error("[主成员打卡]用户{}收益结算失败！", userId, e);
        }
    }

    /**
     * 主成员没打卡当日收益结算
     *
     * 主成员抽成 ＝ 家庭成员额外空投 × 抽成比例
     *
     * 主成员最终额外空投 ＝  主成员总抽成 － 队长抽成
     *
     * 主成员——组长
     *
     * @param date 当日
     * @param stat 队伍打卡统计
     * @param teamName 队伍名称
     * @param userId 主成员用户id
     * @param main 家庭主成员奖励抽成
     * @param captain 队长奖励抽成
     * @param familyMemberUserIds 家庭成员（2_爸爸 3_妈妈 4_配偶）用户id集合
     * @param userList 队伍中打了卡的用户id集
     */
    private void unPunchAndMainMemberIncomeSettlement(LocalDate date, TeamPunchStat stat, String teamName, Long userId, BigDecimal main, BigDecimal captain, List<Long> familyMemberUserIds,List<Long> userList) {
        try{
            // 1.主成员总抽成
            BigDecimal mainIncome = getMainIncome(date,stat.getMemberGain(),main,familyMemberUserIds,stat.getTeamId(),userList);
            // 2.队长抽成
            BigDecimal finalIncome = mainIncome.multiply(ONE_INCOME.subtract(captain)).setScale(2, RoundingMode.HALF_UP);
            // 记录收益结算和发放积分
            grantIncome(stat, teamName, userId, ZERO_INCOME, finalIncome, mainIncome.setScale(2, RoundingMode.HALF_UP));
        }catch (Exception e){
            log.error("[主成员没打卡]用户{}收益结算失败！", userId, e);
        }
    }

    /**
     * 队长【管理员】打卡当日收益结算
     *
     * 队长抽成 ＝ 主成员额外空投 × 抽成比例
     *
     * 队长最终额外空投 ＝ 额外空投 ＋ 队长总抽成
     *
     * @param date 当日
     * @param stat 队伍打卡统计
     * @param teamName 队伍名称
     * @param userId 队长用户id
     * @param main 家庭主成员奖励抽成
     * @param captain 队长奖励抽成
     * @param mainMemberMap <主成员用户id, 家庭成员用户id集合>
     * @param userList 队伍中打了卡的用户id集
     */
    private void punchAndAdminMemberIncomeSettlement(LocalDate date, TeamPunchStat stat, String teamName, Long userId, BigDecimal main, BigDecimal captain, Map<Long, List<Long>> mainMemberMap,List<Long> userList) {
        try{
            // 计算用户队伍内打卡总收益
            BigDecimal totalIncome = punchUserRecordService.getUserTotalIncome(userId, Long.valueOf(stat.getTeamId()), date);
            // 计算发放积分
            // 1.额外空投
            BigDecimal income = stat.getMemberGain().multiply(totalIncome);
            // 2.队长总抽成
            BigDecimal mainIncomeTotal = getMainIncomeTotal(date, stat.getMemberGain(), main, captain, mainMemberMap, stat.getTeamId(),userList ,userId);
            // 3.队长最终额外空投
            BigDecimal finalIncome = income.add(mainIncomeTotal).setScale(2, RoundingMode.HALF_UP);
            // 记录收益结算和发放积分
            grantIncome(stat, teamName, userId, totalIncome, finalIncome, mainIncomeTotal.setScale(2, RoundingMode.HALF_UP));
        }catch (Exception e){
            log.error("[主成员打卡]用户{}收益结算失败！", userId, e);
        }
    }

    /**
     * 队长【管理员】没打卡当日收益结算
     *
     * 队长最终额外空投 ＝  队长总抽成
     *
     * @param date 当日
     * @param stat 队伍打卡统计
     * @param teamName 队伍名称
     * @param userId 队长用户id
     * @param main 家庭主成员奖励抽成
     * @param captain 队长奖励抽成
     * @param mainMemberMap <主成员用户id, 家庭成员用户id集合>
     * @param userList 队伍中打了卡的用户id集
     */
    private void unPunchAndAdminMemberIncomeSettlement(LocalDate date, TeamPunchStat stat, String teamName, Long userId, BigDecimal main, BigDecimal captain, Map<Long, List<Long>> mainMemberMap,List<Long> userList) {
        try{
            // 1.队长总抽成
            BigDecimal mainIncomeTotal = getMainIncomeTotal(date, stat.getMemberGain(), main, captain, mainMemberMap, stat.getTeamId(),userList,userId).setScale(2, RoundingMode.HALF_UP);
            // 记录收益结算和发放积分
            grantIncome(stat, teamName, userId, ZERO_INCOME, mainIncomeTotal, mainIncomeTotal);
        }catch (Exception e){
            log.error("[主成员没打卡]用户{}收益结算失败！", userId, e);
        }
    }

    /**
     * 获取队伍中所有主成员的总抽成
     *
     * @param date 当日
     * @param memberGain 打卡增益(用来计算用户的额外空投)
     * @param main 家庭主成员奖励抽成
     * @param captain 队长奖励抽成
     * @param mainMemberMap <主成员用户id, 家庭成员用户id集合>
     * @param teamId 队伍id
     * @param userList 队伍中打了卡的用户id集
     * @param userId 队长id
     * @return 所有主成员的总抽成
     */
    public BigDecimal getMainIncomeTotal(LocalDate date, BigDecimal memberGain,BigDecimal main, BigDecimal captain, Map<Long, List<Long>> mainMemberMap,Integer teamId,List<Long> userList,Long userId){
        BigDecimal captainIncome = ZERO_INCOME;//队长总抽成
        for (Map.Entry<Long, List<Long>> entry : mainMemberMap.entrySet()) {
            Long mainMemberId = entry.getKey();
            List<Long> familyMemberIds = entry.getValue();
            // 计算用户队伍内打卡总收益(需要判断当前主成员是否为队长,若为队长则不加入计算)
            BigDecimal totalIncome = (userList.contains(mainMemberId) && !Objects.equals(mainMemberId, userId)) ? punchUserRecordService.getUserTotalIncome(mainMemberId, Long.valueOf(teamId), date):ZERO_INCOME;
            // 主成员额外空投
            BigDecimal mainMemberIncome = memberGain.multiply(totalIncome);
            // 主成员的总抽成
            BigDecimal familyMembersIncome = getMainIncome(date,memberGain,main,familyMemberIds,teamId,userList);
            // 队长抽成(如过主成员用户id为队长用户id,则不在需要队长抽成了)
            BigDecimal captainAssembly = Objects.equals(mainMemberId, userId)? mainMemberIncome.add(familyMembersIncome):mainMemberIncome.add(familyMembersIncome).multiply(captain);
            // 统计
            captainIncome = captainIncome.add(captainAssembly);
        }
        return captainIncome;
    }

    /**
     * 获取队伍中某个主成员的总抽成
     *
     * 主成员抽成 ＝ 家庭成员额外空投 × 抽成比例
     *
     * @param date 当日
     * @param memberGain 打卡增益(用来计算用户的额外空投)
     * @param main 抽成比例
     * @param familyMemberUserIds 家庭成员（2_爸爸 3_妈妈 4_配偶）用户id集合
     * @param teamId 队伍id
     * @param userList 队伍中打了卡的用户id集
     * @return 主成员总抽成
     */
    private BigDecimal getMainIncome(LocalDate date,BigDecimal memberGain, BigDecimal main, List<Long> familyMemberUserIds,Integer teamId,List<Long> userList) {
        BigDecimal mainIncome = ZERO_INCOME;
        if (CollUtil.isNotEmpty(familyMemberUserIds)) {
            for (Long userId : familyMemberUserIds) {
                if (userList.contains(userId)) { //只有该家庭的成员打卡了，才有收益
                    // 计算用户队伍内打卡总收益
                    BigDecimal totalIncome = punchUserRecordService.getUserTotalIncome(userId, Long.valueOf(teamId), date);
                    // 额外空投
                    BigDecimal income = memberGain.multiply(totalIncome);
                    // 主成员抽成
                    BigDecimal assembly = income.multiply(main);
                    // 统计
                    mainIncome = mainIncome.add(assembly);
                }
            }
        }
        return mainIncome;
    }

    /**
     * 记录收益结算和发放积分
     *
     * @param stat 某队伍打卡统计
     * @param teamName 某队伍名称
     * @param userId 打卡用户id
     * @param totalIncome 用户打卡当日总收益
     * @param income 打卡用户收益
     * @param assemblyIncome 今日抽成收益（只争对于【队长-管理员】和【组长-主成员】存在）
     */
    private void grantIncome(TeamPunchStat stat, String teamName, Long userId, BigDecimal totalIncome, BigDecimal income, BigDecimal assemblyIncome) {
        if(totalIncome.compareTo(ZERO_INCOME)==0 && assemblyIncome.compareTo(ZERO_INCOME)==0 && income.compareTo(ZERO_INCOME)==0){
            return;
        }
        // 记录收益结算
        UserTeamSettleRecord record = new UserTeamSettleRecord();
        record.setUserId(userId);
        record.setTeamId(stat.getTeamId());
        record.setTeamPunchStatId(stat.getId());
        record.setTodayIncome(totalIncome);
        record.setAssemblyIncome(assemblyIncome);
        record.setSettleIncome(income);
        record.setStatDate(stat.getStatDate());
        record.setMemberGain(stat.getMemberGain());
        record.setIsRead(false);
        boolean save = userTeamSettleRecordService.save(record);
        if(save){
            // 发放积分
            userWalletService.increase(userId, UserWalletEnum.INTEGRAL, income, WalletChangeTypeEnum.TEAM, record.getId(), StringUtils.format(WalletChangeTypeEnum.TEAM.getMsg(), teamName));
        }
    }

}
